using System;
using System.Text.RegularExpressions;

namespace needle.Services.astra.Common
{
	/// <summary>
	/// Regular expression text match with support for matches inversion.
	/// </summary>
	public class TextMatch : IEquatable<TextMatch>
	{
		#region Constructors

		/// <summary>
		/// Constructor.
		/// </summary>
		/// <param name="fullText">See <see cref="FullText"/>.</param>
		/// <param name="matchFrom">See <see cref="MatchFrom"/>.</param>
		/// <param name="matchTo">See <see cref="MatchTo"/>.</param>
		public TextMatch(string fullText, Match matchFrom, Match matchTo)
		{
			this._fullText = fullText;
			this._matchFrom = matchFrom;
			this._matchTo = matchTo;
		}
		/// <summary>
		/// Constructor - normal match.
		/// </summary>
		/// <param name="fullText">See <see cref="FullText"/>.</param>
		/// <param name="match">Used for setting both <see cref="MatchFrom"/> and <see cref="MatchTo"/>.</param>
		public TextMatch(string fullText, Match match)
			: this(fullText, match, match)
		{
		}
		/// <summary>
		/// Constructor - full text match.
		/// Sets both <see cref="MatchFrom"/> and <see cref="MatchTo"/> to null.
		/// </summary>
		/// <param name="fullText">See <see cref="FullText"/>.</param>
		/// <param name="matchFrom">See <see cref="MatchFrom"/>.</param>
		/// <param name="matchTo">See <see cref="MatchTo"/>.</param>
		public TextMatch(string fullText)
			: this(fullText, null)
		{
		}

		#endregion

		/// <summary>
		/// Matched part of <see cref="FullText"/>.
		/// </summary>
		public string Value
		{
			get
			{
				int index = this.Index;
				if (index < 0)
					return null;
				int length = this.Length;
				if (length < 0)
					return null;
				return this.FullText.Substring(index, length);
			}
		}
		/// <summary>
		/// Starting index of the match.
		/// </summary>
		/// <value>If &lt; 0, match is not valid.</value>
		public int Index
		{
			get
			{
				int index = -1;
				if (this.MatchFrom != null)
				{
					// one match
					if (this.MatchFrom == this.MatchTo)
						index = this.MatchFrom.Index;
					// between two matches
					else
						index = this.MatchFrom.Index + this.MatchFrom.Length;
				}
				// from the beginning
				else
					index = 0;
				if (this.FullText == null || this.FullText.Length <= index)
					return -1;
				else
					return index;
			}
		}
		/// <summary>
		/// Length (number of <see cref="char"/>acters) of the match.
		/// </summary>
		/// <value>Can be 0.</value>
		public int Length
		{
			get
			{
				int index = this.Index;
				if (index < 0)
					return 0;
				int length = 0;
				if (this.MatchTo != null)
				{
					// one match
					if (this.MatchFrom == this.MatchTo)
						length = this.MatchFrom.Length;
					// between two matches
					else
						length = this.MatchTo.Index - index;
				}
				// to the end
				else
					length = this.FullText.Length - index;
				if (length < 0 || this.FullText.Length < index + length)
					return 0;
				else
					return length;
			}
		}
		/// <summary>
		/// Whether this match is valid (<see cref="Index"/> &gt;= 0).
		/// </summary>
		public bool IsValid
		{
			get { return this.Index >= 0; }
		}
		/// <summary>
		/// Whether this match represents whole <see cref="FullText"/> value.
		/// </summary>
		public bool IsFullTextMatch
		{
			get { return this.FullText.Length == this.Length; }
		}
		/// <summary>
		/// Full text this match is in.
		/// </summary>
		public string FullText
		{
			get { return _fullText; }
		}
		/// <summary>
		/// Starting regular expression match.
		/// </summary>
		/// <value>
		/// If null, this match starts at index 0.
		/// If <see cref="MatchFrom"/> is not null and equals <see cref="MatchTo"/>, this match is represented by <see cref="MatchFrom"/>.
		/// If <see cref="MatchFrom"/> is not equal to <see cref="MatchTo"/>, this match represents text between those two regular expression matches (inverted results).
		/// </value>
		public Match MatchFrom
		{
			get { return _matchFrom; }
		}
		/// <summary>
		/// Ending regular expression match.
		/// </summary>
		/// <value>
		/// If null, this match ends at the end of <see cref="FullText"/>.
		/// If <see cref="MatchFrom"/> is not null and equals <see cref="MatchTo"/>, this match is represented by <see cref="MatchFrom"/>.
		/// If <see cref="MatchFrom"/> is not equal to <see cref="MatchTo"/>, this match represents text between those two regular expression matches (inverted results).
		/// </value>
		public Match MatchTo
		{
			get { return _matchTo; }
		}

		#region IEquatable<TextMatch> Members

		/// <summary>
		/// Whether this object equals <paramref name="other"/>.
		/// Compares <see cref="FullText"/>, <see cref="Index"/> and <see cref="Length"/>.
		/// </summary>
		/// <param name="other">Object to compare this object to.</param>
		/// <returns>true if both objects are equal.</returns>
		public bool Equals(TextMatch other)
		{
			return other != null &&
				this.FullText == other.FullText &&
				this.Index == other.Index &&
				this.Length == other.Length;
		}

		#endregion

		/// <summary>
		/// See <see cref="FullText"/>.
		/// </summary>
		private string _fullText;
		/// <summary>
		/// See <see cref="MatchFrom"/>.
		/// </summary>
		private Match _matchTo;
		/// <summary>
		/// See <see cref="MatchTo"/>.
		/// </summary>
		private Match _matchFrom;
	}
}
